// 这是添加了 位图传参 可以重定向的 shell

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIZE 1024     //获取命令行指令的字符串数组的最大存储空间
#define MAX_ARGV 64   //用于限制最大可以分割出64个命令行字符串
#define SEP " "       //定义分割字符
#define filename log.txt
#define 


char *argv[MAX_ARGV];    //保存切割后的命令行字符串的指针数组
char pwd[SIZE];
char env[SIZE]; // for test  环境变量在每次导入的时候都会被覆盖，所以就没法多次导入，用这个数组就可以实现多次导入
int lastcode = 0;



//获取几个环境变量用于打印命令行提示需要包含头文件 #include <stdlib.h>
//获取主机名
const char* HostName()
{
    char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "None";//如果非空返回主机名，如果为空，返回None
}
//获取用户名
const char* UserName()
{
    char *username = getenv("USER");
    if(username) return username;
    else return "None";
}
//获取当前路径
const char* CurrentWorkDir()
{
    char *pwd = getenv("PWD");
    if(pwd) return pwd;
    else return "None";
}
//获取根目录所在路径
const char* Home()
{
    return getenv("HOME");
}

int Interactive(char out[], int size)
{
    //输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);          //用于获取命令行输入的指令
    out[strlen(out)-1] = 0; //'\0' 用于除去空行 
    return strlen(out);
}

//2、拆分命令行指令 用strtok函数来切割 第一次使用是传要被切割的str,第二次使用的时候不传str，而是传NULL
void Split(char in[])
{
    // 首先在拆分命令时就有四种情况
    // ls -a -l 
    // ls -a -l >  log.txt   输入重定向
    // ls -a -l >> log.txt   追加重定向
    // ls -a -l <  log.txt   输出重定向
    // 分为左半部分->指令 , 右半部分->重定向
    
    
    int i=0;
    argv[i++] = strtok(in, SEP);
    while(argv[i++] = strtok(NULL, SEP));  //这里不是在做==判断，而是赋值，因为前面设置了commandline的结尾处为'\0',所以当i指向最后一个字符串时，while就会停下来
    if(strcmp(argv[0], "ls") == 0)
    {
      argv[i-1] = (char*)"--color";
      argv[i] = NULL;
    }


}


//3、执行命令行指令  本进程是一个命令行字符串解释器，如果直接把自己替换成了一个命令的进程，就没法进行多次命令的执行，故而不可以直接进行进程替换，可以创建一个子进程来实现
void Excute() 
{
    pid_t id = fork();
    if(id == 0)
    {
        //让子进程执行命令
        //选择execvp 用vp是最好的，因为我们手上有argv 就是需要执行的命令行参数 
        execvp(argv[0], argv);  
        exit(1);                //如果没有替换进程成功，则设置退出码为1
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); //把最近一次执行的命令退出码保存起来，主要是内建命令echo需要用到
}

int BuildinCmd()
{
    int ret = 0;
    //1、检测是否是内建命令，挤兑内建命令做穷举
    if(strcmp("cd",argv[0]) == 0)
    { 
        ret  = 1;
        const char *target = argv[1];  //这里是 cd 所带的命令，如果为空说明要回到根目录，不为空则进入指定目录就行
        if(!target)  target = Home();
        //chdir系统提供的 更改调用进程自身所在的路径
        chdir(target);
        char temp[1024];
        getcwd(temp, 1024);
        snprintf(pwd, SIZE, "PWD=%s", temp);        //用snprintf函数（按照"PWD=%s"的指定格式把字符串temp拷贝指定长度SIZE到pwd里）来实现拼接字符串
        putenv(pwd);                               // 这里再把拼接好的字符串好的导入到环境变量里，在下一次打印命令行的时候，就可以实现路径的切换了
    
    }
    // export是导环境变量的命令，所以必须是到给自己，即为内建命令
    else if(strcmp("export",argv[0]) == 0)
    {
        //env查看环境变量的指令，因为导环境变量必须导给父进程自己，这样才能继承给子进程
        ret = 1;
        //if(argv[1]) putenv(argv[1]);  //直接到环境变量会导致覆盖，
        if(argv[1])
        {
          strcpy(env, argv[1]);
          putenv(env);
        }
    }
    else if(strcmp("echo",argv[0]) == 0)
    {
      ret = 1;
      if(argv[1] == NULL)
      {
        printf("\n");
      }
      else{
        if(argv[1][0] == '$')
        {
          if(argv[1][1] == '?')
          {
            printf("%d\n", lastcode);
            lastcode = 0;
          }
          else{
            char *e = getenv(argv[1]+1);
            if(e) printf("%s\n", *e);
          }
        }
        else{
          printf("%s\n", argv[1]);
        }
      }

    }
   return ret;
}


int main()
{
    //实现一个shell有四步
    while(1)    
    {   
        char commandline[SIZE];
        //1、打印命令行提示符，获取用户输入的命令行指令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
        //2、拆分命令行指令
        Split(commandline);
        //3.处理内建命令
        n = BuildinCmd(); // 是内建命令 返回1
        if(n) continue;   // if里面值为1，则本次命令是内建命令，已经由父进程执行，无需在创建子进程再次执行
        //4、执行命令行指令  
        Excute();
        //5、用while(1) 死循环 使得命令行能够循环跑起来
    }    
    //（在测试时发现cd,echo等命令无法执行，其实这些都是内建指令，需要本进程（本shell来实现），而其他大部分指令都只需要用子进程来实现即可）
    return 0;
}





















//下面的代码只实现了用子进程替换为命令行要执行的代码的进程，故而像cd等命令无法使用，前面的实现了较为完整的功能的
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//#include <unistd.h>
//#include <sys/types.h>
//#include <sys/wait.h>
//
//#define SIZE 1024     //获取命令行指令的字符串数组的最大存储空间
//#define MAX_ARGV 64   //用于限制最大可以分割出64个命令行字符串
//#define SEP " "       //定义分割字符
//
//char *argv[MAX_ARGV];    //保存切割后的命令行字符串的指针数组
//
////获取几个环境变量用于打印命令行提示需要包含头文件 #include <stdlib.h>
////获取主机名
//const char* HostName()
//{
//    char *hostname = getenv("HOSTNAME");
//    if(hostname) return hostname;
//    else return "None";//如果非空返回主机名，如果为空，返回None
//}
////获取用户名
//const char* UserName()
//{
//    char *username = getenv("USER");
//    if(username) return username;
//    else return "None";
//}
////获取当前路径
//const char* CurrentWorkDir()
//{
//    char *pwd = getenv("PWD");
//    if(pwd) return pwd;
//    else return "None";
//}
//
//int Interactive(char out[], int size)
//{
//    //输出提示符并获取用户输入的命令字符串"ls -a -l"
//    printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());
//    fgets(out, size, stdin);          //用于获取命令行输入的指令
//    out[strlen(out)-1] = 0; //'\0' 用于除去空行 
//    return strlen(out);
//}
//
//
//
//
//    //2、拆分命令行指令 用strtok函数来切割 第一次使用是传要被切割的str,第二次使用的时候不传str，而是传NULL
//    
//void Split(char in[])
//{
//    int i=0;
//    argv[i++] = strtok(in, SEP);
//    while(argv[i++] = strtok(NULL, SEP));       //这里不是在做==判断，而是赋值，因为前面设置了commandline的结尾处为'\0',所以当i指向最后一个字符串时，while就会停下来
//}
//
//
//    //3、执行命令行指令  本进程是一个命令行字符串解释器，如果直接把自己替换成了一个命令的进程，就没法进行多次命令的执行，故而不可以直接进行进程替换，可以创建一个子进程来实现
//void Excute() 
//{
//    pid_t id = fork();
//    if(id == 0)
//    {
//        //让子进程执行命令
//        //选择execvp 用vp是最好的，因为我们手上有argv 就是需要执行的命令行参数 
//        execvp(argv[0], argv);  
//        exit(1);                //如果没有替换进程成功，则设置退出码为1
//    }
//    waitpid(id, NULL, 0);
//}
//
//
//int main()
//{
//    //实现一个shell有四步
//    while(1)    
//    {   
//        char commandline[SIZE];
//        //1、打印命令行提示符，获取用户输入的命令行指令字符串
//        int n = Interactive(commandline, SIZE);
////        if(n == 0) continue;
//        //2、拆分命令行指令
//        Split(commandline);
//        //3、执行命令行指令  
//        Excute();
//        //4、使得命令行能够循环跑起来
//    }    
//    //（在测试时发现cd,echo等命令无法执行，其实这些都是内建指令，需要本进程（本shell来实现），而其他大部分指令都只需要用子进程来实现即可）
//    return 0;
//}
////1、打印命令行提示符，获取命令行指令
////2、拆分命令行指令
////3、执行命令行指令
////4、使得命令行能够循环跑起来
////（在测试时发现cd,echo等命令无法执行，其实这些都是内建指令，需要本进程（本shell来实现），而其他大部分指令都只需要用子进程来实现即可）
